﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using FFmpeg.AutoGen;

namespace FFmpegVideoClip.Lib
{
    public unsafe class VolumeDetector
    {
        private static readonly Regex VolumeMatch = new Regex("(?<=max_volume:\\s*)-?\\d\\.?\\d", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        public float MaxVolume_volumedetect;
        //public float MaxVolume_CustomAlgorithm;
        private unsafe void LogHandler(void* ptr, int level, string format, byte* vl)
        {
            if (level > ffmpeg.AV_LOG_INFO) return;
            byte* buffer = stackalloc byte[1024];
            int printPrefix = 1;
            try
            {
                ffmpeg.av_log_format_line(ptr, level, format, vl, buffer, 1024, &printPrefix);
                string str = Encoding.UTF8.GetString(buffer, 1024);
                if (!string.IsNullOrWhiteSpace(str) && float.TryParse(VolumeMatch.Match(str).Value, out float maxVolume))
                    MaxVolume_volumedetect = maxVolume;
            }
            catch (Exception ex)
            {
                LogManage.WriteExceptionInfo("VolumeDetector日志回调错误:", ex);
            }
        }
        public static VolumeDetector DetectVolume(string inputFile, CancellationToken cancellationToken, TimeSpan? startTime, TimeSpan? endTime)
        {
            if (startTime is null) startTime = TimeSpan.Zero;
            if (endTime is null) endTime = TimeSpan.MaxValue;
            int revalue = 0;
            VolumeDetector vd = new VolumeDetector();
            AVFrame* pFrame = null;
            AVPacket* pPacket = null;
            AVFrame* outFrame = null;
            AVCodecContext* pCodecContext = null;
            AVFormatContext* pFormatContext = null;
            AVFilterGraph* pFilterGraph = null;
            AVFilterContext* pBufferSrcCtx;
            AVFilterContext* pvolumedetectCtx;
            AVFilterContext* pOutputCtx;
            //SwrContext* swrCtx = null;

            try
            {
                // 初始化FFmpeg库
                ffmpeg.av_log_set_level(ffmpeg.AV_LOG_INFO);
                //ffmpeg.avformat_network_init();
                //ffmpeg.av_log_set_callback((av_log_set_callback_callback_func)vd.LogHandler);

                // 打开输入文件
                pFormatContext = ffmpeg.avformat_alloc_context();
                if ((revalue = ffmpeg.avformat_open_input(&pFormatContext, inputFile, null, null)) != 0)
                    throw new FFmpegVolumedetectException($"无法打开输入文件:{FFmpegHelper.av_strerror(revalue)}");

                // 获取流信息
                if ((revalue = ffmpeg.avformat_find_stream_info(pFormatContext, null)) < 0)
                    throw new FFmpegVolumedetectException($"无法获取流信息:{FFmpegHelper.av_strerror(revalue)}");

                // 查找音频流
                int audioStreamIndex = -1;
                for (int i = 0; i < pFormatContext->nb_streams; i++)
                {
                    if (pFormatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
                    {
                        audioStreamIndex = i;
                        break;
                    }
                }
                if (audioStreamIndex == -1)
                    throw new FFmpegVolumedetectException($"未找到音频流:{FFmpegHelper.av_strerror(revalue)}");
                AVStream* audioStream = pFormatContext->streams[audioStreamIndex];

                // 获取解码器
                AVCodecParameters* pCodecParameters = pFormatContext->streams[audioStreamIndex]->codecpar;
                AVCodec* pCodec = ffmpeg.avcodec_find_decoder(pCodecParameters->codec_id);
                if (pCodec == null)
                    throw new FFmpegVolumedetectException($"未找到解码器:{FFmpegHelper.av_strerror(revalue)}");

                // 分配解码器上下文
                pCodecContext = ffmpeg.avcodec_alloc_context3(pCodec);
                ffmpeg.avcodec_parameters_to_context(pCodecContext, pCodecParameters);
                if ((revalue = ffmpeg.avcodec_open2(pCodecContext, pCodec, null)) < 0)
                    throw new FFmpegVolumedetectException($"无法打开解码器:{FFmpegHelper.av_strerror(revalue)}");

                AVChannelLayout MonoChannelLayout;
                ffmpeg.av_channel_layout_from_string(&MonoChannelLayout, "mono");

                //outFrame->format = (int)AVSampleFormat.AV_SAMPLE_FMT_S16;
                //outFrame->sample_rate = pCodecContext->sample_rate;
                //outFrame->ch_layout = MonoChannelLayout;
                //outFrame->time_base = pFormatContext->streams[audioStreamIndex]->time_base;

                //// 初始化重采样器
                //swrCtx = ffmpeg.swr_alloc();
                //ffmpeg.swr_alloc_set_opts2(
                //&swrCtx,
                //&outFrame->ch_layout,               // 输出声道布局
                //(AVSampleFormat)outFrame->format,   // 输出格式
                //outFrame->sample_rate,              // 输出采样率

                //&pCodecContext->ch_layout,          // 输入声道布局
                //pCodecContext->sample_fmt,          // 输入格式
                //pCodecContext->sample_rate,         // 输入采样率
                //0, null);
                //ffmpeg.swr_init(swrCtx).ThrowExceptionIfError();


                // 创建过滤器图
                pFilterGraph = ffmpeg.avfilter_graph_alloc();


                // 创建buffer源过滤器
                byte* cl_name = stackalloc byte[64];
                ffmpeg.av_channel_layout_describe(&pCodecContext->ch_layout, cl_name, 64);
                string str = Encoding.UTF8.GetString(cl_name, 64);
                //string args = $"time_base={outFrame->time_base.num}/{outFrame->time_base.den}:sample_rate={outFrame->sample_rate}:" +
                //  $"sample_fmt={ffmpeg.av_get_sample_fmt_name((AVSampleFormat)outFrame->format)}:channel_layout=mono";
                string args = $"time_base={pCodecContext->time_base.num}/{pCodecContext->time_base.den}:sample_rate={pCodecContext->sample_rate}:" +
                  $"sample_fmt={ffmpeg.av_get_sample_fmt_name(pCodecContext->sample_fmt)}:channel_layout={str}";

                if ((revalue = ffmpeg.avfilter_graph_create_filter(&pBufferSrcCtx, ffmpeg.avfilter_get_by_name("abuffer"), "src", args, null, pFilterGraph)) < 0)
                    throw new FFmpegVolumedetectException($"无法创建buffer源过滤器:参数[{args}]:{FFmpegHelper.av_strerror(revalue)}");

                // 创建volumedetect过滤器
                //string filterArgs = $"start_time={startTime.TotalSeconds}:end_time={endTime.TotalSeconds}";
                if ((revalue = ffmpeg.avfilter_graph_create_filter(&pvolumedetectCtx, ffmpeg.avfilter_get_by_name("volumedetect"), "volumedetect", null, null, pFilterGraph)) < 0)
                    throw new FFmpegVolumedetectException($"无法创建volumedetect过滤器:{FFmpegHelper.av_strerror(revalue)}");

                // 1.创建 abuffersink 作为输出终点
                if ((revalue = ffmpeg.avfilter_graph_create_filter(&pOutputCtx, ffmpeg.avfilter_get_by_name("abuffersink"), "output", null, null, pFilterGraph)) < 0)
                    throw new FFmpegVolumedetectException($"无法创建 abuffersink 过滤器:{FFmpegHelper.av_strerror(revalue)}");

                // 2. 先连接 src -> volumedetect
                if ((revalue = ffmpeg.avfilter_link(pBufferSrcCtx, 0, pvolumedetectCtx, 0)) < 0)
                    throw new FFmpegVolumedetectException($"无法连接 src -> volumedetect:{FFmpegHelper.av_strerror(revalue)}");

                // 3. 再连接 volumedetect -> abuffersink
                if ((revalue = ffmpeg.avfilter_link(pvolumedetectCtx, 0, pOutputCtx, 0)) < 0)
                    throw new FFmpegVolumedetectException($"无法连接 volumedetect -> abuffersink:{FFmpegHelper.av_strerror(revalue)}");

                // 配置过滤器图
                if ((revalue = ffmpeg.avfilter_graph_config(pFilterGraph, null)) < 0)
                    throw new FFmpegVolumedetectException($"配置过滤器图_avfilter_graph_config失败:{FFmpegHelper.av_strerror(revalue)}");


                // 读取并处理数据包
                pPacket = ffmpeg.av_packet_alloc();
                pFrame = ffmpeg.av_frame_alloc();

                // 跳转到指定开始时间

                long StartTimestamp = (long)(startTime.Value.TotalSeconds * audioStream->time_base.den / audioStream->time_base.num);
                if ((revalue = ffmpeg.av_seek_frame(pFormatContext, audioStreamIndex, StartTimestamp, ffmpeg.AVSEEK_FLAG_BACKWARD)) < 0)
                    throw new FFmpegVolumedetectException($"无法跳转到指定时间:{FFmpegHelper.av_strerror(revalue)}");

                //int bytes_per_sample = ffmpeg.av_get_bytes_per_sample((AVSampleFormat)outFrame->format).Dump();
                //ffmpeg.av_get_bytes_per_sample((AVSampleFormat)outFrame->format).Dump();

                // short* samples;
                // short current = 0, max = 0;
                // float sumOfSquares = 0;
                // int Length = 0;
                if (cancellationToken.IsCancellationRequested)
                    return new();
                outFrame = ffmpeg.av_frame_alloc();
                while ((revalue = ffmpeg.av_read_frame(pFormatContext, pPacket)) >= 0)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        ffmpeg.av_packet_unref(pPacket);
                        ffmpeg.av_frame_unref(pFrame);
                        return new();
                    }
                        
                    if (pPacket->stream_index == audioStreamIndex)
                    {
                        // 解码数据包
                        if ((revalue = ffmpeg.avcodec_send_packet(pCodecContext, pPacket)) < 0)
                        {
                            ffmpeg.av_packet_unref(pPacket);
                            continue;
                        }

                        while ((revalue = ffmpeg.avcodec_receive_frame(pCodecContext, pFrame)) == 0)
                        {
                            if (cancellationToken.IsCancellationRequested)
                            {
                                ffmpeg.av_packet_unref(pPacket);
                                ffmpeg.av_frame_unref(pFrame);
                                return new();
                            }
                            // 检查是否超到达开始时间    
                            double pts = pFrame->pts * ffmpeg.av_q2d(audioStream->time_base);
                            if (pts < startTime.Value.TotalSeconds)
                            {
                                ffmpeg.av_frame_unref(pFrame);
                                continue;
                            }
                            // 检查是否超过结束时间    
                            if (pts > endTime.Value.TotalSeconds)
                            {
                                ffmpeg.av_packet_unref(pPacket);
                                ffmpeg.av_frame_unref(pFrame);
                                goto End;
                            }
                            //if ((revalue = ffmpeg.swr_convert_frame(swrCtx, outFrame, pFrame)) < 0)
                            //    throw new FFmpegVolumedetectException($"音频重采样异常:{FFmpegHelper.av_strerror(revalue)}");

                            // samples = (short*)outFrame->data[0];
                            // for (int i = 0; i < outFrame->nb_samples; ++i)
                            // {
                            //     ++Length;
                            //     current = *samples;
                            //     sumOfSquares += current * (float)current;
                            //     if (max < Math.Abs(current))
                            //         max = Math.Abs(current);
                            // }

                            //outFrame->best_effort_timestamp = pFrame->best_effort_timestamp;
                            //outFrame->pts = pFrame->pts;
                            //outFrame->pkt_dts = pFrame->pkt_dts;
                            //outFrame->sample_aspect_ratio = pFrame->sample_aspect_ratio;

                            // 将帧发送到过滤器图
                            //if ((revalue = ffmpeg.av_buffersrc_add_frame(pBufferSrcCtx, outFrame)) < 0)
                            //    throw new FFmpegVolumedetectException($"无法向过滤器图添加帧:{FFmpegHelper.av_strerror(revalue)}");
                            if ((revalue = ffmpeg.av_buffersrc_add_frame_flags(pBufferSrcCtx, pFrame, (int)av_buffersrc_add_frame_flagsNume.AV_BUFFERSRC_FLAG_KEEP_REF)) < 0)
                            {
                                ffmpeg.av_packet_unref(pPacket);
                                ffmpeg.av_frame_unref(pFrame);
                                throw new FFmpegVolumedetectException($"无法向过滤器图添加帧:{FFmpegHelper.av_strerror(revalue)}");
                            }
                            ffmpeg.av_frame_unref(pFrame);
                            // 从过滤器图获取处理后的帧
                            while (ffmpeg.av_buffersink_get_frame(pOutputCtx, outFrame) >= 0)
                            {
                                if (cancellationToken.IsCancellationRequested)
                                {
                                    ffmpeg.av_packet_unref(pPacket);
                                    ffmpeg.av_frame_unref(pFrame);
                                    ffmpeg.av_frame_unref(outFrame);
                                    return new();
                                }
                                ffmpeg.av_frame_unref(outFrame);
                                //AVDictionaryEntry* entry = ffmpeg.av_dict_get(outFrame->metadata, "lavfi.volumedetect.mean_volume", null, 0);
                                //if (entry != null && entry->key != null)
                                //{
                                //	string? k = Marshal.PtrToStringAnsi((nint)entry->key);
                                //	string? v = Marshal.PtrToStringAnsi((nint)entry->value);
                                //	$"key-{k} ||value-{v}".Dump();
                                //	// 这里volumedetect过滤器会自动输出音量信息到日志
                                //	//ffmpeg.av_frame_unref(outFrame);
                                //}
                            }
                            ffmpeg.av_frame_unref(outFrame);
                            ffmpeg.av_frame_unref(pFrame);
                        }
                        ffmpeg.av_frame_unref(pFrame);
                    }
                    ffmpeg.av_packet_unref(pPacket);
                }
            End:;
                // max.Dump();
                // (20 * Math.Log10(max)).Dump();
                // float rms = MathF.Sqrt(sumOfSquares / Length);
                // if (rms < 0.0001f)
                // {
                //     //错误返回
                // }
                //int Max16BitValue = 32768;
                // (20 * MathF.Log10(rms / Max16BitValue)).Dump();
                //vd.MaxVolume_CustomAlgorithm = 20 * MathF.Log10((float)max / Max16BitValue);

            }
            catch (Exception ex)
            {
                LogManage.WriteExceptionInfo("FFmpegVolumedetect错误", ex);
                return new();
            }
            finally
            {
                // 先释放所有使用filter graph的资源
                if (pFrame != null)
                    ffmpeg.av_frame_free(&pFrame);

                if (outFrame != null)
                    ffmpeg.av_frame_free(&outFrame);

                if (pPacket != null)
                    ffmpeg.av_packet_free(&pPacket);

                // 释放filter graph 并接收日志消息
                ffmpeg.av_log_set_callback((av_log_set_callback_callback_func)vd.LogHandler);
                if (pFilterGraph != null)
                    ffmpeg.avfilter_graph_free(&pFilterGraph);
                ffmpeg.av_log_set_callback(null);

                // 最后释放其他资源
                //if (swrCtx != null)
                //    ffmpeg.swr_free(&swrCtx);
                if (pCodecContext != null)
                    ffmpeg.avcodec_free_context(&pCodecContext);

                if (pFormatContext != null)
                    ffmpeg.avformat_close_input(&pFormatContext);
            }
            if (cancellationToken.IsCancellationRequested)
                return new();
            return vd;
        }


        [System.Serializable]
        public class FFmpegVolumedetectException : System.Exception
        {
            public FFmpegVolumedetectException() { }
            public FFmpegVolumedetectException(string message) : base(message) { }
            public FFmpegVolumedetectException(string message, System.Exception inner) : base(message, inner) { }
        }
    }

}